/*
 *  Mach-O object file loading
 *
 *  Copyright (c) 2006 Pierre d'Herbemont
 *  Copyright (c) 2010-2011 Tobias Platen
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */
#include "dyld_private.h"
#import <objc/ObjCModule.h>
#import <objc/objc-class.h>
#import <objc/objc_sel.h>
#import <objc/objc_class.h>

#define PTRADD(t,a,b) (t)((int)a+(int)b)
#define PTRSUB(t,a,b) (t)((int)a-(int)b)


static void bind_pointer(void* symaddr,void* ptr)
{
    void** ptr2=(void**) ptr;
    *ptr2=symaddr;
}

static void bind_jump_table(void* symaddr,void* ptr)
{
    int rel32=PTRSUB(int,symaddr,((char*)ptr+5));
    char* ptr2=(char*)ptr;
    ptr2[0]=0xE9;//jmp instuction
    ptr2[1]=rel32 & 0xFF;
    ptr2[2]=(rel32 >>8) & 0xFF;
    ptr2[3]=(rel32 >>16) & 0xFF;
    ptr2[4]=(rel32 >>24) & 0xFF;
}

static section* getSect(struct segment_command* seg,char* sectname)
{
    struct section* sect=(struct section*)(((int)seg)+sizeof(struct segment_command));
    int j;
    for(j=0;j<seg->nsects;j++)
    {
		if(strcmp(sect[j].sectname,sectname)==0) return &sect[j];
    }
    return NULL;
}









int ImageLoaderMachO::link_section(struct section* sect,int size,void (*bindfunc)(void*,void*))
{
    if(sect==0) return 0;//nothing to do
    int slide=0;

    //;
    //if(mach_hdr==0) mach_hdr=(struct mach_header*)info->slide; //if mach_hdr ==0 we are a slidable image

    struct nlist* nl=(struct nlist*)(((int)mach_hdr)+symtab->symoff);
    char* strtab=(char*)(((int)mach_hdr)+symtab->stroff);

    uint32_t*   indirectTable=(uint32_t*)(dysymtab->indirectsymoff+(int)mach_hdr);
    uint32_t indirectTableOffset=sect->reserved1;
    int offset=0;
    for(int i=0;i<sect->size/size;i++)
    {
        int index=indirectTable[indirectTableOffset + i];
        char* sym=strtab+nl[index].n_strx;
        char* ptrToBind=(char*)sect->addr+slide;
        ptrToBind+=offset;
	//TODO symaddr
	void* bindaddr=getImportedDylibSymbolAddress(sym);
	DYLD_DEBUG(printf("bind %s at %p to %p\n",sym,ptrToBind,bindaddr));
        bindfunc(bindaddr,ptrToBind);//
        offset+=size;
    }
    return 0;
}


ImageLoader* ImageLoaderMachO::loadDylib(llvm::MachO::dylib_command* dylib)
{
	char* name=(char*)(((int)dylib)+dylib->dylib.name);
	ImageLoader* ldr=loaderForPath(name);
	if(ldr!=NULL) return ldr;
	DYLD_DEBUG(printf("TODO loadDylib:%s\n",name));
	return NULL;
}

int ImageLoaderMachO::loadDylibs()
{
    struct load_command* lc=(struct load_command*)((int)(mach_hdr)+sizeof(struct mach_header));//FIXME 64 bit
    int i;
    int ret=-1;
    int d=0;
    for(i=0 ; i < (mach_hdr->ncmds) ; i++)
    {
        switch(lc->cmd)
	{
        case LoadCommandDylibLoad:
            d++;
            break;
	}
        lc = (struct load_command*)((int)(lc)+(lc->cmdsize));
    }
    dylibs=new ImageLoader*[d];
    ndylibs=d;	
    d=0;
    lc=(struct load_command*)((int)(mach_hdr)+sizeof(struct mach_header));
    for(i=0 ; i < (mach_hdr->ncmds) ; i++)
    {
        switch(lc->cmd)
	{
        case LoadCommandDylibLoad:
            dylibs[d]=loadDylib((struct dylib_command*)lc);
            d++;
            ret=0;
            break;
	}
        lc = (struct load_command*)((int)(lc)+(lc->cmdsize));
    }
    return ret;
}

struct segment_command* ImageLoaderMachO::getSegment(char* segname)
{
    struct load_command* lcmds=(struct load_command*)(((int)(mach_hdr))+sizeof(struct mach_header));//FIXME 64 bit
    struct load_command* lc;
    struct segment_command* seg;
    int i;	
    for(i=0, lc = lcmds; i < (mach_hdr->ncmds) ; i++)
    {
	if(lc->cmd==LoadCommandSegment32)
	{
	  seg= (struct segment_command*)lc;
          if(strcmp(seg->segname,segname)==0) return seg;
	  
	}
        lc = (struct load_command*)((int)(lc)+(lc->cmdsize));
    }
    return NULL;
}




int ImageLoaderMachO::link()
{
    getImportedDylibSymbolAddress("ImageLoaderMachO__link__all_dylibs");
    //if(mach_hdr==0) mach_hdr=(struct mach_header*)info->slide; //if mach_hdr ==0 we are a slidable image		
    struct load_command* lc=(struct load_command*)(((int)(mach_hdr))+sizeof(struct mach_header));//FIXME 64 bit
    

    const struct macho_segment_command* seg = NULL;
	
    int i;
    for(i=0;i < ndylibs;i++)
    {
	ImageLoader* dylib=dylibs[i];
	if(dylib)
	{ 
	  dylib->link();	
	}
    }	
    for(i=0 ; i < (mach_hdr->ncmds) ; i++)
    {
        switch(lc->cmd)
	{
        case LoadCommandSymtab:
            symtab=(struct symtab_command*)lc;
            break;
        case LoadCommandDynamicSymtabInfo:
            dysymtab=(struct dysymtab_command*)lc;
            break;
	//case LoadCommandSegment32:
	//	seg=(struct macho_segment_command*)lc;
	//    break;	snow leopard
	}
        lc = (struct load_command*)((int)(lc)+(lc->cmdsize));
    }
#if 1
    llvm::MachO::segment_command* __IMPORT=(llvm::MachO::segment_command*)getSegment("__IMPORT");
    if(__IMPORT==NULL)
    {
	printf("cannot run old openstep binaries\n");
	exit(0);
    }	
    llvm::MachO::section* __IMPORT__jump_table=getSect(__IMPORT,"__jump_table");
    llvm::MachO::section* __IMPORT__pointers=getSect(__IMPORT,"__pointers");	
    if(symtab && dysymtab && __IMPORT)
    {
	int slide=0;
	mprotect(PTRADD(void*,__IMPORT->vmaddr,slide),__IMPORT->vmsize,PROT_WRITE|PROT_READ|PROT_EXEC);
	if(__IMPORT__jump_table) if(link_section(__IMPORT__jump_table,5,::bind_jump_table)) return 1;
	if(__IMPORT__pointers) if(link_section(__IMPORT__pointers,4,::bind_pointer)) return 1;
    }
    llvm::MachO::segment_command* __OBJC=(llvm::MachO::segment_command*)getSegment("__OBJC");
    if(__OBJC)
    {
		llvm::MachO::section* __OBJC__message_refs=getSect(__OBJC,"__message_refs");
		llvm::MachO::section* __OBJC__module_info=getSect(__OBJC,"__module_info");	
		llvm::MachO::section* __OBJC__cls_refs=getSect(__OBJC,"__cls_refs");		
		int slide=0;
		mprotect(PTRADD(void*,__OBJC->vmaddr,slide),__OBJC->vmsize,PROT_WRITE|PROT_READ|PROT_EXEC);//?
		void (*_dyld_link_objc)(int dyldversion,int nmess,SEL* sels,int nmods,OBJCModule* mods,int nrefs,Class* refs);
		_dyld_link_objc=(void (*)(int,int,SEL*,int,OBJCModule*,int,Class*))loaderForPath("/usr/lib/libobjc.A.dylib")->getExportedSymbolAddress("__dyld_link_objc");

		
		 int base=0;
                 if(_dyld_link_objc==0)  {printf("broken libobjc\n");exit(1);}
        	 _dyld_link_objc(1,__OBJC__message_refs->size/sizeof(SEL),PTRADD(SEL*,base,__OBJC__message_refs->addr),
                            	   __OBJC__module_info->size/sizeof(OBJCModule),PTRADD(OBJCModule*,base,__OBJC__module_info->addr),
                            	   __OBJC__cls_refs->size/sizeof(Class),PTRADD(Class*,base,__OBJC__cls_refs->addr));
		llvm::MachO::segment_command* __DATA=(llvm::MachO::segment_command*)getSegment("__DATA");

		llvm::MachO::section* __DATA__cfstring=getSect(__DATA,"__cfstring");
		
		if(__DATA__cfstring)
	        {
		
		id (*_objc_getClass)(const char *name);
		_objc_getClass =(id (*)(const char*)) loaderForPath("/usr/lib/libobjc.A.dylib")->getExportedSymbolAddress("_objc_getClass");
		id constclass=_objc_getClass("NSDarwinString");
		
		if(constclass)
		{
			
			
			printf("addr=%x count=%i\n",__DATA__cfstring->addr,__DATA__cfstring->size/16);
			id* cfstrings=(id*)__DATA__cfstring->addr;
			for(int k=0;k<__DATA__cfstring->size/16;k++)
			{
				cfstrings[k*16]=constclass;
			}
		}
		else
		DYLD_WARN(printf("class NSDarwinString not found\n"));
		
	        }	
	
	 
    }	
#endif
    //FIXME;
    return 0;
}

ImageLoaderMachO::ImageLoaderMachO(int fd,int fa_offset,int fa_size,int nsegments,struct segment_command** segments,uint32_t type,uint32_t flags)
{
	void* vmaddr=0;
	for(int i=0;i<nsegments;i++)
	{
		DYLD_DEBUG(printf("segment %s\n",segments[i]->segname));
		if(strcmp(segments[i]->segname,"__TEXT")==0) vmaddr=(void*)segments[i]->vmaddr;
	}

    int mflags=MAP_PRIVATE;
    int memsize;
    if(fa_size) memsize=fa_size;
    else 
    { 
	lseek(fd, 0, SEEK_SET);
	memsize=lseek(fd, 0, SEEK_END);
    }
    
    if(memsize % PAGE_SIZE !=0 ) memsize += (PAGE_SIZE - memsize % PAGE_SIZE);
    if(type==HeaderFileTypeExecutable) mflags|=MAP_FIXED;


    if(getenv("DYLD_DEBUG_MAPS"))
    {
        printf("cat /proc/%i/maps\n",getpid());
        while(1);
    }

    void* addr=mmap(vmaddr,memsize,PROT_READ|PROT_WRITE,mflags,fd,fa_offset);

    if(addr==(void*)-1) 
    {
	printf("mmap failed, on ubuntu try sudo sysctl -w vm.mmap_min_addr=0\n");
	exit(-1);
    }
    
    mach_hdr=(mach_header*)addr;
    ndylibs=0;
    dylibs=NULL;
    symtab=NULL;
    dysymtab=NULL;
    close(fd);			
  


}

void* ImageLoaderMachO::getImportedDylibSymbolAddress(char* s)
{
	void* ret=NULL;
	for(int i=0;i<ndylibs;i++)
	{
		ImageLoader* l=dylibs[i];
		if(l) ret=l->getExportedSymbolAddress(s);
		if(ret) return ret;
	}
	return ret;
	
}

std::string ImageLoaderMachO::getFilename()
{
	return "";
}
std::string ImageLoaderMachO::getInstallname()
{
	return "";
}

void* ImageLoaderMachO::loadThread()
{
    struct load_command* lc=(struct load_command*)(((int)(mach_hdr))+sizeof(struct mach_header));
    int i;
    void* entry=NULL;
    for(i=0 ; i < (mach_hdr->ncmds) ; i++)
    {
        switch(lc->cmd)
	{
        case LoadCommandUnixThread:
            {
		int* th=(int*)((int)lc+sizeof(struct load_command));
		DYLD_DEBUG(printf("load thread %x %x %x %x\n",th[11],th[12],th[10],mach_hdr));
		entry=(void*)th[12];
		
            }
            break;
	}
        lc = (struct load_command*)((int)(lc)+(lc->cmdsize));
    }
    if(entry==NULL) return 0; 
    //printf("cat /proc/%i/maps\n",getpid());
    //    while(1);
    unsigned char* ret=((unsigned char*)entry)+49;
    if(*ret==0xE8) return (void*)ret;
    return 0;
}
void* ImageLoaderMachO::getExportedSymbolAddress(char*)
{
	return 0;
}
